import Vue from "./Vue";
import { Watcher } from "./Watcher";

export class Compiler {
  private el: Element;
  private vm: any;

  constructor(vm: Vue) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el);
  }

  compile(el: Element) {
    const child_nodes: NodeListOf<ChildNode> = el.childNodes;
    child_nodes.forEach((node) => {
      if (this.isTextNode(node)) {
        this.compileText(node);
      } else if (this.isElementNode(node)) {
        this.compileElement(node as Element);
      }

      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node as Element);
      }
    });
  }

  compileElement(node: Element) {
    const attrs = node.attributes;
    Array.from(attrs).forEach((attr) => {
      if (this.isDirective(attr.name)) {
        const attr_name = attr.name.replace("v-", "");
        const attr_value = attr.value;
        this.render(attr_name, node, attr_value);
      }
    });
  }

  render(type: string, node: Element, key: string) {
    const handler: string = `${type}Render`;
    // @ts-ignore
    this[handler](node, key);
  }

  textRender(node: HTMLElement, key: string) {
    node.textContent = this.vm[key];

    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue;
    });
  }

  modelRender(node: HTMLInputElement, key: string) {
    node.value = this.vm[key];

    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    });

    node.addEventListener("input", () => {
      this.vm[key] = node.value;
    });
  }

  compileText(node: Node) {
    const node_value: string | null = node.nodeValue;
    if (node_value) {
      const reg = /{{(.+?)}}/;
      if (reg.test(node_value)) {
        const key = RegExp.$1.trim();
        node.nodeValue = node_value.replace(reg, this.vm[key]);

        new Watcher(this.vm, key, (newValue) => {
          node.nodeValue = newValue;
        });
      }
    }
  }

  isDirective(attrName: string): boolean {
    return attrName.startsWith("v-");
  }

  isTextNode(node: Node): boolean {
    return node.nodeType === node.TEXT_NODE;
  }

  isElementNode(node: Node): boolean {
    return node.nodeType === node.ELEMENT_NODE;
  }
}
